package net.siufung.web.aop.aspect;

import cn.hutool.core.util.ArrayUtil;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.PropertyFilter;
import com.google.common.base.Stopwatch;
import com.google.common.collect.Lists;
import lombok.AllArgsConstructor;
import net.siufung.core.exception.BizServiceException;
import net.siufung.core.utils.StringPool;
import net.siufung.security.api.AuthUtil;
import net.siufung.web.aop.annotation.AutoLog;
import net.siufung.web.aop.dto.AutoLogDto;
import net.siufung.web.aop.service.IAutoLogService;
import net.siufung.web.aop.service.IAutoLogType;
import net.siufung.web.utils.WebUtil;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.http.HttpMethod;
import org.springframework.validation.BindingResult;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * @author 陈建峰
 * @date 2022/10/4 12:01 上午
 * @since V1.0.0
 */
@Aspect
@AllArgsConstructor
public class AutoLogAspect {

    private final IAutoLogService autoLogService;
    private final IAutoLogType autoLogType;

    @Pointcut("@annotation(net.siufung.web.aop.annotation.AutoLog)")
    public void autoLogPointCut() {}

    @Around("autoLogPointCut()")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        Stopwatch stopwatch = Stopwatch.createStarted();
        Object proceed = proceedingJoinPoint.proceed();
        long elapsed = stopwatch.stop().elapsed(TimeUnit.MILLISECONDS);
        autoLogStore(proceedingJoinPoint, elapsed);
        return proceed;
    }

    private void autoLogStore(ProceedingJoinPoint proceedingJoinPoint, long elapsed){
        MethodSignature signature = (MethodSignature) proceedingJoinPoint.getSignature();
        Method method = signature.getMethod();
        AutoLogDto autoLogDto = new AutoLogDto();
        AutoLog autoLog = method.getAnnotation(AutoLog.class);
        int paramsMaxLength = 500;
        if(Objects.nonNull(autoLog)){
            autoLogDto.setTitle(autoLog.value());
            autoLogDto.setDescription(autoLog.description());
            autoLogDto.setMethodEventEnum(autoLog.methodEvent());
            paramsMaxLength = autoLog.paramsMaxLength();
        }
        String className = proceedingJoinPoint.getTarget().getClass().getName();
        String methodName = signature.getName();
        autoLogDto.setMethod(className + StringPool.DOT + methodName +
                StringPool.LEFT_BRACKET + StringPool.RIGHT_BRACKET);
        HttpServletRequest request = WebUtil.getRequest();
        if(Objects.isNull(request)){
            throw new BizServiceException("request is null");
        }
        autoLogDto.setParams(getRequestParams(request, proceedingJoinPoint, paramsMaxLength));
        autoLogDto.setUri(request.getRequestURI());
        autoLogDto.setIp(WebUtil.getIp(request));
        autoLogDto.setBrowser(WebUtil.getBrowserName(request) + StringPool.SLASH + WebUtil.getBrowserVersion(request));
        autoLogDto.setTime(elapsed);
        if(Objects.nonNull(AuthUtil.getCurrentUser())){
            autoLogDto.setOperatorId(AuthUtil.getUserId());
            autoLogDto.setOperatorName(AuthUtil.getUsername());
        }
        if(Objects.nonNull(autoLogType) && Objects.nonNull(autoLogType.getValue())){
            autoLogDto.setAutoLogType(autoLogType);
        }
        autoLogService.asyncRecord(autoLogDto);
    }

    private String getRequestParams(HttpServletRequest request, JoinPoint joinPoint, int paramsMaxLength) {
        StringBuilder params = new StringBuilder(StringPool.EMPTY);
        String httpMethod = request.getMethod();
        Object[] args = joinPoint.getArgs();
        if (HttpMethod.POST.name().equals(httpMethod) || HttpMethod.PUT.name().equals(httpMethod)
                || HttpMethod.PATCH.name().equals(httpMethod)) {
            List<Object> argList = Lists.newArrayList(args);
            if (ArrayUtil.isNotEmpty(args)) {
                for (Object object : args) {
                    if (object instanceof BindingResult || object instanceof ServletRequest ||
                            object instanceof ServletResponse || object instanceof MultipartFile) {
                        continue;
                    }
                    argList.add(object);
                }
            }
            params = new StringBuilder(
                    JSONObject.toJSONString(argList, (PropertyFilter) (o, name, value) ->
                            Objects.isNull(value) || value.toString().length() <= paramsMaxLength));
        } else {
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            Method method = signature.getMethod();
            LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer();
            String[] paramNames = u.getParameterNames(method);
            if (ArrayUtil.isNotEmpty(args) && ArrayUtil.isNotEmpty(paramNames)) {
                for (int i = 0; i < args.length; i++) {
                    params.append(StringPool.SPACE).append(paramNames[i]).append(StringPool.COLON).append(args[i]);
                }
            }
        }
        return params.toString();
    }


}
